﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Noris.Tools.FrxEditor.Components
{
    #region struct Point3D : Definice 3D bodu
    /// <summary>
    /// Point3D : Definice 3D bodu
    /// </summary>
    public struct Point3D
    {
        /// <summary>
        /// Konstruktor bodu 3D
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        public Point3D(float x, float y, float z)
        {
            this._X = x;
            this._Y = y;
            this._Z = z;
        }
        private float _X;
        private float _Y;
        private float _Z;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Point3D: " + this.Text;
        }
        /// <summary>
        /// Vizualizace datové části bez úvodního textu popisujícího třídu
        /// </summary>
        public string Text { get { return String.Format("X={0}; Y={1}; Z={2}", this.X, this.Y, this.Z); } }
        /// <summary>
        /// Souřadnice bodu v ose X (doprava)
        /// </summary>
        public float X { get { return _X; } set { _X = value; } }
        /// <summary>
        /// Souřadnice bodu v ose Y (dolů, jsme we Windows grafice)
        /// </summary>
        public float Y { get { return _Y; } set { _Y = value; } }
        /// <summary>
        /// Souřadnice bodu v ose Z (dozadu)
        /// </summary>
        public float Z { get { return _Z; } set { _Z = value; } }
    }
    #endregion
    #region struct Size3D : Definice 3D rozměru
    /// <summary>
    /// Size3D : Definice 3D rozměru
    /// </summary>
    public struct Size3D
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="depth"></param>
        public Size3D(float width, float height, float depth)
        {
            this._Width = width;
            this._Height = height;
            this._Depth = depth;
        }
        private float _Width;
        private float _Height;
        private float _Depth;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Size3D: " + this.Text;
        }
        /// <summary>
        /// Vizualizace datové části bez úvodního textu popisujícího třídu
        /// </summary>
        public string Text { get { return String.Format("Width={0}; Height={1}; Depth={2}", this.Width, this.Height, this.Depth); } }
        /// <summary>
        /// Šířka (kladná = doprava)
        /// </summary>
        public float Width { get { return _Width; } set { _Width = value; } }
        /// <summary>
        /// Výška (kladná = dolů, jsme we Windows grafice)
        /// </summary>
        public float Height { get { return _Height; } set { _Height = value; } }
        /// <summary>
        /// Hloubka (dozadu)
        /// </summary>
        public float Depth { get { return _Depth; } set { _Depth = value; } }
    }
    #endregion
    #region struct Block3D : Definice 3D prostoru
    /// <summary>
    /// Block3D : Definice 3D prostoru
    /// </summary>
    public struct Block3D
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="location"></param>
        /// <param name="size"></param>
        public Block3D(Point3D location, Size3D size)
        {
            this._Location = location;
            this._Size = size;
            this._Points = null;
            this._Sides = null;
        }
        private Point3D _Location;
        private Size3D _Size;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Block3D: " + this.Text;
        }
        /// <summary>
        /// Vizualizace datové části bez úvodního textu popisujícího třídu
        /// </summary>
        public string Text { get { return this.Location.Text + "; " + this.Size.Text; } }
        #region Empty objekt
        /// <summary>
        /// Obsahuje Empty objekt.
        /// </summary>
        public static Block3D Empty { get { return new Block3D(new Point3D(0f, 0f, 0f), new Size3D(0f, 0f, 0f)); } }
        #endregion
        #region Vizuální property
        /// <summary>
        /// Výchozí bod (počátek).
        /// Pokud má kvádr kladné rozměry, je to levý horní přední roh.
        /// </summary>
        public Point3D Location { get { return _Location; } set { _Location = value; } }
        /// <summary>
        /// Velikost objektu.
        /// </summary>
        public Size3D Size { get { return _Size; } set { _Size = value; _Invalidate(); } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Left.
        /// Lze setovat.
        /// </summary>
        public float X1 { get { return _Location.X; } set { _Location.X = value; _Invalidate(); } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Top.
        /// Lze setovat.
        /// </summary>
        public float Y1 { get { return _Location.Y; } set { _Location.Y = value; _Invalidate(); } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Front.
        /// Lze setovat.
        /// </summary>
        public float Z1 { get { return _Location.Z; } set { _Location.Z = value; _Invalidate(); } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Right.
        /// Nelze setovat.
        /// </summary>
        public float X2 { get { return _Location.X + _Size.Width; } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Bottom.
        /// Nelze setovat.
        /// </summary>
        public float Y2 { get { return _Location.Y + _Size.Height; } }
        /// <summary>
        /// U normálního objektu obsahuje souřadnici Back.
        /// Nelze setovat.
        /// </summary>
        public float Z2 { get { return _Location.Z + _Size.Depth; } }
        /// <summary>
        /// Obsahuje šířku objektu (v ose X)
        /// Lze setovat.
        /// Může být záporná.
        /// </summary>
        public float Width { get { return _Size.Width; } set { _Size.Width = value; _Invalidate(); } }
        /// <summary>
        /// Obsahuje výšku objektu (v ose Y)
        /// Lze setovat.
        /// Může být záporná.
        /// </summary>
        public float Height { get { return _Size.Height; } set { _Size.Height = value; _Invalidate(); } }
        /// <summary>
        /// Obsahuje hloubku objektu (v ose Z).
        /// Lze setovat.
        /// Může být záporná.
        /// </summary>
        public float Depth { get { return _Size.Depth; } set { _Size.Depth = value; _Invalidate(); } }
        /// <summary>
        /// Bod uprostřed objektu.
        /// </summary>
        public Point3D Center { get { return new Point3D(this.X1 + this.Width / 2f, this.Y1 + this.Height / 2f, this.Z1 + this.Depth / 2f); } }
        #endregion
        #region Normalizace, reversace
        /// <summary>
        /// Obsahuje (vrátí) Block3D, jehož rozměry nejsou záporné, a který se fyzicky nachází na identických místech jako this.
        /// Jinými slovy: v případě záporné velikost (Width, Height, Depth) převrátí souřadnice (X1 - X2; atd) v dané ose.
        /// </summary>
        public Block3D Normalized
        {
            get
            {
                float w = this.Width;
                float h = this.Height;
                float d = this.Depth;
                float x = (w >= 0f ? this.X1 : this.X2);
                float y = (h >= 0f ? this.Y1 : this.Y2);
                float z = (d >= 0f ? this.Z1 : this.Z2);
                return new Block3D(new Point3D(x, y, z), new Size3D((w < 0f ? -w : w), (h < 0f ? -h : h), (d < 0f ? -d : d)));
            }
        }
        /// <summary>
        /// Obsahuje tentýž prostor, ale převrácený v ose X (prohodí souřadnice X1 a X2)
        /// </summary>
        public Block3D ReverseX { get { return new Block3D(new Point3D(this.X2, this.Y1, this.Z1), new Size3D(-this.Width, this.Height, this.Depth)); } }
        /// <summary>
        /// Obsahuje tentýž prostor, ale převrácený v ose Y (prohodí souřadnice Y1 a Y2)
        /// </summary>
        public Block3D ReverseY { get { return new Block3D(new Point3D(this.X1, this.Y2, this.Z1), new Size3D(this.Width, -this.Height, this.Depth)); } }
        /// <summary>
        /// Obsahuje tentýž prostor, ale převrácený v ose Z (prohodí souřadnice Z1 a Z2)
        /// </summary>
        public Block3D ReverseZ { get { return new Block3D(new Point3D(this.X1, this.Y1, this.Z2), new Size3D(this.Width, this.Height, -this.Depth)); } }
        #endregion
        #region Krajní body (Point3D), boční strany (Polygon3D)
        /// <summary>
        /// Points: rohy kvádru.
        /// </summary>
        public Dictionary<Block3DVertex, Point3D> Points
        {
            get
            {
                if (_Points == null)
                {
                    _Points = new Dictionary<Block3DVertex, Point3D>();
                    _Points.Add(Block3DVertex.LTF, new Point3D(X1, Y1, Z1));
                    _Points.Add(Block3DVertex.RTF, new Point3D(X2, Y1, Z1));
                    _Points.Add(Block3DVertex.RBF, new Point3D(X2, Y2, Z1));
                    _Points.Add(Block3DVertex.LBF, new Point3D(X1, Y2, Z1));
                    _Points.Add(Block3DVertex.LTH, new Point3D(X1, Y1, Z2));
                    _Points.Add(Block3DVertex.RTH, new Point3D(X2, Y1, Z2));
                    _Points.Add(Block3DVertex.RBH, new Point3D(X2, Y2, Z2));
                    _Points.Add(Block3DVertex.LBH, new Point3D(X1, Y2, Z2));
                }
                return new Dictionary<Block3DVertex, Point3D>(_Points);
            }
        }
        /// <summary>
        /// Sides: stěny kvádru.
        /// Názvy stěn jsou platné tehdy, pokud rozměry jsou kladné.
        /// Rozměry totiž mohou být i záporné, pak orientace stěn v daném (záporném) směru je opačná.
        /// </summary>
        public Dictionary<Block3DSide, Polygon3D> Sides
        {
            get
            {
                if (_Sides == null)
                {
                    Dictionary<Block3DVertex, Point3D> points = this.Points;

                    _Sides = new Dictionary<Block3DSide, Polygon3D>();
                    _Sides.Add(Block3DSide.Frontal, new Polygon3D(true, points[Block3DVertex.LTF], points[Block3DVertex.RTF], points[Block3DVertex.RBF], points[Block3DVertex.LBF], points[Block3DVertex.LTF]));
                    _Sides.Add(Block3DSide.Back, new Polygon3D(true, points[Block3DVertex.LTH], points[Block3DVertex.RTH], points[Block3DVertex.RBH], points[Block3DVertex.LBH], points[Block3DVertex.LTH]));
                    _Sides.Add(Block3DSide.Left, new Polygon3D(true, points[Block3DVertex.LTF], points[Block3DVertex.LTH], points[Block3DVertex.LBH], points[Block3DVertex.LBF], points[Block3DVertex.LTF]));
                    _Sides.Add(Block3DSide.Right, new Polygon3D(true, points[Block3DVertex.RTF], points[Block3DVertex.RTH], points[Block3DVertex.RBH], points[Block3DVertex.RBF], points[Block3DVertex.RTF]));
                    _Sides.Add(Block3DSide.Top, new Polygon3D(true, points[Block3DVertex.LTF], points[Block3DVertex.RTF], points[Block3DVertex.RTH], points[Block3DVertex.LTH], points[Block3DVertex.LTF]));
                    _Sides.Add(Block3DSide.Bottom, new Polygon3D(true, points[Block3DVertex.LBF], points[Block3DVertex.RBF], points[Block3DVertex.RBH], points[Block3DVertex.LBH], points[Block3DVertex.LBF]));
                }
                return new Dictionary<Block3DSide, Polygon3D>(_Sides);
            }
        }
        private Dictionary<Block3DVertex, Point3D> _Points;
        private Dictionary<Block3DSide, Polygon3D> _Sides;
        /// <summary>
        /// Invaliduje vnitřní paměti (_Points a _Sides).
        /// Příští čtení Points a Sides je vygeneruje znovu na základě aktuálních dat.
        /// </summary>
        private void _Invalidate()
        {
            this._Points = null;
            this._Sides = null;
        }
        #endregion
        /// <summary>
        /// Určí, zda strana (side) je viditelná z daného pozorovacího bodu.
        /// Předpokládá se viditelnost zvenku objektu, nepředpokládá se otevřený objekt (odstraněná strana a nahlížení dovnitř).
        /// Strana je viditelná, pokud přiměřená souřadnice pozorovatele je kladná.
        /// Pro shodu souřadnic (například nahlížení přesně z boku) se strana jeví neviditelná (její šířka je nulová).
        /// </summary>
        /// <param name="side">Dotazovaná strana. Pro stranu Back se vždy vrací false.</param>
        /// <param name="observerPoint">Souřadnice pozorovatele.
        /// Pozorovatel je typicky na záporné souřadnici v ose Z.
        /// Pokud souřadnice pozorovatele Z je shodná nebo větší než this.Z1, pak není viditelná žádná strana.</param>
        /// <returns></returns>
        internal bool IsSideVisibleFromPoint(Block3DSide side, Point3D observerPoint)
        {
            switch (side)
            {
                case Block3DSide.Frontal:
                    return (this.Z1 > observerPoint.Z);
                case Block3DSide.Back:
                    return false;
                case Block3DSide.Left:
                    return (this.Z1 > observerPoint.Z && this.X1 > observerPoint.X);
                case Block3DSide.Right:
                    return (this.Z1 > observerPoint.Z && this.X2 < observerPoint.X);
                case Block3DSide.Top:
                    return (this.Z1 > observerPoint.Z && this.Y1 > observerPoint.Y);
                case Block3DSide.Bottom:
                    return (this.Z1 > observerPoint.Z && this.Y2 < observerPoint.Y);
            }
            return false;
        }
    }
    #endregion
    #region struct Cylinder3D : Definice 3D elipsy (válec)
    /// <summary>
    /// Cylinder3D : Definice 3D elipsy (válec)
    /// </summary>
    public struct Cylinder3D
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="center"></param>
        /// <param name="radius"></param>
        /// <param name="height"></param>
        public Cylinder3D(Point3D center, float radius, float height)
        {
            this._Center = center;
            this._Radius = radius;
            this._Height = height;
        }
        private Point3D _Center;
        private float _Radius;
        private float _Height;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Cylinder3D: " + this.Text;
        }
        /// <summary>
        /// Vizualizace datové části bez úvodního textu popisujícího třídu
        /// </summary>
        public string Text { get { return "Center=" + this.Center.Text + "; Radius=" + this.Radius.ToString() + "; Height=" + this.Height.ToString(); } }
        #region Empty objekt
        /// <summary>
        /// Obsahuje Empty objekt.
        /// </summary>
        public static Cylinder3D Empty { get { return new Cylinder3D(new Point3D(0f, 0f, 0f), 0f, 0f); } }
        #endregion
        #region Vizuální property
        /// <summary>
        /// Střed cylindru.
        /// </summary>
        public Point3D Center { get { return _Center; } set { _Center = value; } }
        /// <summary>
        /// Poloměr elipsy.
        /// </summary>
        public float Radius { get { return _Radius; } set { _Radius = value; } }
        /// <summary>
        /// Poloměr elipsy.
        /// </summary>
        public float Height { get { return _Height; } set { _Height = value; } }
        #endregion
    }
    #endregion
    #region enums Block3DVertex, Block3DSide
    /// <summary>
    /// Hrana kvádru. Left, Right, Top, Bottom, Front, Hind
    /// </summary>
    public enum Block3DVertex
    {
        /// <summary>Žádná</summary>
        None = 0,
        /// <summary>Left - Top - Front</summary>
        LTF,
        /// <summary>Right - Top - Front</summary>
        RTF,
        /// <summary>Right - Bottom - Front</summary>
        RBF,
        /// <summary>Left - Bottom - Front</summary>
        LBF,
        /// <summary>Left - Top - Hind</summary>
        LTH,
        /// <summary>Right - Top - Hind</summary>
        RTH,
        /// <summary>Right - Bottom - Hind</summary>
        RBH,
        /// <summary>Left - Bottom - Hind</summary>
        LBH
    }
    /// <summary>
    /// Stěna ve 3D prostoru
    /// </summary>
    public enum Block3DSide
    {
        /// <summary>Žádná</summary>
        None = 0,
        /// <summary>
        /// Přední stěna, jejíž souřadnice Z = Z1
        /// </summary>
        Frontal,
        /// <summary>
        /// Zadní stěna, jejíž souřadnice Z = Z2
        /// </summary>
        Back,
        /// <summary>
        /// Levá stěna, jejíž souřadnice X = X1
        /// </summary>
        Left,
        /// <summary>
        /// Pravá stěna, jejíž souřadnice X = X2
        /// </summary>
        Right,
        /// <summary>
        /// Horní stěna, jejíž souřadnice Y = Y1
        /// </summary>
        Top,
        /// <summary>
        /// Dolní stěna, jejíž souřadnice Y = Y2
        /// </summary>
        Bottom
    }
    #endregion
    #region struct Line3D : Definice 3D přímky / úsečky
    /// <summary>
    /// Line3D : Definice 3D přímky / úsečky
    /// </summary>
    public struct Line3D
    {
        /// <summary>
        /// Konstruktor přímky
        /// </summary>
        /// <param name="pointA"></param>
        /// <param name="pointB"></param>
        public Line3D(Point3D pointA, Point3D pointB)
        {
            this._PointA = pointA;
            this._PointB = pointB;
        }
        /// <summary>
        /// Bod 1
        /// </summary>
        public Point3D PointA { get { return _PointA; } set { _PointA = value; } }
        /// <summary>
        /// Bod 2
        /// </summary>
        public Point3D PointB { get { return _PointB; } set { _PointB = value; } }
        private Point3D _PointA;
        private Point3D _PointB;
    }
    #endregion
    #region struct Polygon3D : sada 3D bodů
    /// <summary>
    /// Polygon3D : sada 3D bodů
    /// </summary>
    public struct Polygon3D
    {
        /// <summary>
        /// Konstruktor polygonu
        /// </summary>
        /// <param name="closed"></param>
        /// <param name="points"></param>
        public Polygon3D(bool closed, params Point3D[] points)
        {
            this._Closed = closed;
            this._Points = points;
        }
        private bool _Closed;
        private Point3D[] _Points;
        /// <summary>
        /// Příznak, že polygon je uzavřený (tj. je spojený mezi posledním a prvním bodem)
        /// </summary>
        public bool Closed { get { return _Closed; } set { _Closed = value; } }
        /// <summary>
        /// Soupis bodů polygonu
        /// </summary>
        public Point3D[] Points { get { return _Points; } set { _Points = value; } }
    }
    #endregion
    #region class View3D : Konvertor souřadnic 3D na 2D na základě pozorovacího bodu a průmětny.
    /// <summary>
    /// View3D : Konvertor souřadnic 3D na 2D na základě pozorovacího bodu a průmětny.
    /// Pozorovací bod lze určit kdekoliv v prostoru, přičemž souřadnice Z musí být záporná a vyjadřuje předsunutí pozorovatele před plátnem průmětny.
    /// Průmětna je plocha ve směru X × Y, na souřadnici Z = 0.
    /// </summary>
    public class View3D
    {
        /// <summary>
        /// Vytvoří View3D na základě explicitně daného bodu pozorovatele vzhledem k rovině, jejíž souřadnice Z = 0
        /// </summary>
        /// <param name="observerPoint"></param>
        public View3D(Point3D observerPoint)
        {
            _ObserverPoint = observerPoint;
        }
        /// <summary>
        /// Vytvoří View3D na základě souřadnic pozorovaného výseku (viewArea),
        /// pro relativní pozici pozorovatele vzhledem k viewArea (relativeObserverPoint),
        /// kde pozorovatel je v jisté relativní vzdálenosti od pozorované plochy, dané poměrem perspektivy (perspectiveRatio).
        /// </summary>
        /// <param name="viewArea">Pozorovaná plocha</param>
        /// <param name="relativeObserverPoint">Pozice pozorovatele, kde 0 odpovídá viewArea.X nebo Y, 1 odpovídá viewArea.Right nebo Bottom, kde relativní hodnota může být menší než 0 (doleva / nahoru) i větší než 1.</param>
        /// <param name="perspectiveRatio">Poměr perspektivního zkreslení: 0 je nekonečno, a 1 je přímo uprostřed obrazu (hodnota 1.000 a více není přípustná, stejně jako hodnota menší než 0)</param>
        public View3D(RectangleF viewArea, PointF relativeObserverPoint, float perspectiveRatio)
        {
            PointF observerXY = viewArea.GetPointFAbsolute(relativeObserverPoint);
            double viewDiagonal = Math.Sqrt((double)(viewArea.Width * viewArea.Width + viewArea.Height * viewArea.Height));
            double distance = 0d;
            if (perspectiveRatio <= 0.001f)
                distance = 1000d;
            else if (perspectiveRatio >= 0.99f)
                distance = 1f;
            else
                distance = 1d / (double)perspectiveRatio;
            float observerZ = (float)(distance * viewDiagonal);
            _ObserverPoint = new Point3D(observerXY.X, observerXY.Y, -observerZ);
        }
        private Point3D _ObserverPoint;
        /// <summary>
        /// Souřadnice pozorovatele. Pozorovatel hledí ve směru osy Z k jejím kladným hodnotám.
        /// </summary>
        public Point3D ObserverPoint { get { return _ObserverPoint; } set { _ObserverPoint = value; } }
        /// <summary>
        /// Provede převod 3D souřadnic daného polygonu do pozorovatelných bodů na 2D průmětně
        /// </summary>
        /// <param name="polygon"></param>
        /// <returns></returns>
        public System.Drawing.Drawing2D.GraphicsPath TranslateToPath(Polygon3D polygon)
        {
            System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
            path.AddPolygon(this.Translate(polygon));
            return path;
        }
        /// <summary>
        /// Provede převod 3D souřadnic daného polygonu do pozorovatelných bodů na 2D průmětně
        /// </summary>
        /// <param name="polygon"></param>
        /// <returns></returns>
        public PointF[] Translate(Polygon3D polygon)
        {
            _ObserverPointCheck();
            List<PointF> result = new List<PointF>();
            foreach (Point3D point in polygon.Points)
            {
                PointF? pt = this.Translate(point);
                if (pt.HasValue)
                    result.Add(pt.Value);
                else
                    result.Add(PointF.Empty);
            }
            return result.ToArray();
        }
        /// <summary>
        /// Provede převod 3D souřadnic daného bodu do pozorovatelných bodů na 2D průmětně
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public PointF? Translate(Point3D point)
        {
            _ObserverPointCheck();

            // Převádíme zvlášť souřadnici X a zvlášť Y, přičemž souřadnice Z bodu point slouží pro určení koeficientu vzdálenosti na průmětnu vzhledem k pozorovateli.
            // K tomu patří graf z bočního pohledu, platný pro výpočet X stejně jako Y, kde vodorovná ose reprezentuje osu Z, kde vlevo (záporná strana) je pozorovatel, svisle je zobrazená osa X nebo Y.
            // Pro následující účely budeme souřadnici X a Y nazývat "D" = distance.
            // Pozorovatel (OBSERVER) leží na záporné souřadnici Z (Zobs), na libovolné souřadnici D.
            // Pozorovaný bod (POINT) leží na libovolné souřadnici, jejíž Z > Zobs a na libovolné souřadnici D.
            // Průmět bodu POINT na průmětnu (plocha, jejíž souřadnice Z = 0) určíme jednoduchým trojúhelníkem, kde:
            //  vlevo je bod OBSERVER, 
            //  vodorovně je vzdálenost Z od bodu OBSERVER k POINT,
            //  svisle je posun bodu POINT od pozorovatele OBSERVER ve směru D.
            //  Potom průmět = boid VIEW leží na souřadnici Z = 0, ve vzdálenosti D přímo úměrné vzdálenosti pozorovatele od průmětny.

            // Pokud ale bod POINT leží na opačné straně od bodu OBSERVER než je průmětna (anebo na stejné souřadnici Z), pak bod není viditelný a vrací se null !!!
            float zLen = point.Z - this._ObserverPoint.Z;
            if (zLen <= 0f) return null;

            // Výchozí vzorec, kde D je X nebo Y, point je souřadnice bodu 3D, view je pozice bodu 2D ve směru D (X, Y):
            //   Dview = Dobs + (((Dpoint - Dobs) / (Zpoint - Zobs)) * (-Zobs))
            // Ze vzorce lze izolovat část 1 / (Zpoint - Zobs) * (-Zobs), která vyjadřuje poměr změny rozměru D mezi bodem POINT, průmětnou a pozorovatelem.
            // Z toho rozdíl (Zpoint - Zobs) máme spočítaný v zLen.
            // Tento poměr platí pro pro souřadnici X i Y:
            float transZ = this._ObserverPoint.Z / zLen;
            float viewX = this._ObserverPoint.X - ((point.X - this._ObserverPoint.X) * transZ);
            float viewY = this._ObserverPoint.Y - ((point.Y - this._ObserverPoint.Y) * transZ);

            return new PointF(viewX, viewY);
        }
        private void _ObserverPointCheck()
        {
            if (this._ObserverPoint.Z < 0f)
                return;
            throw new ArgumentOutOfRangeException("ObserverPoint.Z", this._ObserverPoint.Z, "Souřadnice Z bodu ObserverPoint musí být záporná.");
        }
        /// <summary>
        /// Vrátí vzdálenost od pozorovatele k nejbližšímu z daných bodů
        /// </summary>
        /// <param name="points"></param>
        /// <returns></returns>
        public float? FindMinObserverDistance(IEnumerable<Point3D> points)
        {
            float? minDist = null;
            foreach (Point3D point in points)
            {
                float dist = GetObserverDistance(point);
                if (!minDist.HasValue || dist < minDist.Value)
                    minDist = dist;
            }
            return minDist;
        }
        /// <summary>
        /// Vrátí vzdálenost daného bodu od pozorovatele.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public float GetObserverDistance(Point3D point)
        {
            float distX = Math.Abs(point.X - this.ObserverPoint.X);
            float distY = Math.Abs(point.Y - this.ObserverPoint.Y);
            float distZ = Math.Abs(point.Z - this.ObserverPoint.Z);
            double distXY = Math.Sqrt(distX * distX + distY * distY);
            double distXYZ = Math.Sqrt(distXY * distXY + distZ * distZ);
            return (float)distXYZ;
        }
    }
    #endregion
}
